package com.csp.mingyue.common.redis.manager;

import com.csp.mingyue.common.redis.utils.RedisUtils;
import org.redisson.api.RMap;
import org.redisson.api.RMapCache;
import org.redisson.spring.cache.CacheConfig;
import org.redisson.spring.cache.RedissonCache;
import org.springframework.boot.convert.DurationStyle;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.transaction.TransactionAwareCacheDecorator;
import org.springframework.util.StringUtils;

import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * 修改 RedissonSpringCacheManager 源码，重写 cacheName 处理方法 支持多参数
 *
 * @author Strive
 * @date 2023/8/30 16:52
 */
@SuppressWarnings("unchecked")
public class PlusSpringCacheManager implements CacheManager {

	private boolean dynamic = true;

	private boolean allowNullValues = true;

	private boolean transactionAware = true;

	Map<String, CacheConfig> configMap = new ConcurrentHashMap<>();

	ConcurrentMap<String, Cache> instanceMap = new ConcurrentHashMap<>();

	/**
	 * Creates CacheManager supplied by Redisson instance
	 */
	public PlusSpringCacheManager() {
	}

	/**
	 * Defines possibility of storing {@code null} values.
	 * <p>
	 * Default is <code>true</code>
	 * @param allowNullValues stores if <code>true</code>
	 */
	public void setAllowNullValues(boolean allowNullValues) {
		this.allowNullValues = allowNullValues;
	}

	/**
	 * Defines if cache aware of Spring-managed transactions. If {@code true} put/evict
	 * operations are executed only for successful transaction in after-commit phase.
	 * <p>
	 * Default is <code>false</code>
	 * @param transactionAware cache is transaction aware if <code>true</code>
	 */
	public void setTransactionAware(boolean transactionAware) {
		this.transactionAware = transactionAware;
	}

	/**
	 * Defines 'fixed' cache names. A new cache instance will not be created in dynamic
	 * for non-defined names.
	 * <p>
	 * `null` parameter setups dynamic mode
	 * @param names of caches
	 */
	public void setCacheNames(Collection<String> names) {
		if (names != null) {
			for (String name : names) {
				getCache(name);
			}
			dynamic = false;
		}
		else {
			dynamic = true;
		}
	}

	/**
	 * Set cache config mapped by cache name
	 * @param config object
	 */
	public void setConfig(Map<String, ? extends CacheConfig> config) {
		this.configMap = (Map<String, CacheConfig>) config;
	}

	protected CacheConfig createDefaultConfig() {
		return new CacheConfig();
	}

	@Override
	public Cache getCache(String name) {
		Cache cache = instanceMap.get(name);
		if (cache != null) {
			return cache;
		}
		if (!dynamic) {
			return cache;
		}

		CacheConfig config = configMap.get(name);
		if (config == null) {
			config = createDefaultConfig();
			configMap.put(name, config);
		}

		// 重写 cacheName 支持多参数
		String[] array = StringUtils.delimitedListToStringArray(name, "#");
		name = array[0];
		if (array.length > 1) {
			config.setTTL(DurationStyle.detectAndParse(array[1]).toMillis());
		}
		if (array.length > 2) {
			config.setMaxIdleTime(DurationStyle.detectAndParse(array[2]).toMillis());
		}
		if (array.length > 3) {
			config.setMaxSize(Integer.parseInt(array[3]));
		}

		if (config.getMaxIdleTime() == 0 && config.getTTL() == 0 && config.getMaxSize() == 0) {
			return createMap(name, config);
		}

		return createMapCache(name, config);
	}

	private Cache createMap(String name, CacheConfig config) {
		RMap<Object, Object> map = RedisUtils.getClient().getMap(name);

		Cache cache = new RedissonCache(map, allowNullValues);
		if (transactionAware) {
			cache = new TransactionAwareCacheDecorator(cache);
		}
		Cache oldCache = instanceMap.putIfAbsent(name, cache);
		if (oldCache != null) {
			cache = oldCache;
		}
		return cache;
	}

	private Cache createMapCache(String name, CacheConfig config) {
		RMapCache<Object, Object> map = RedisUtils.getClient().getMapCache(name);

		Cache cache = new RedissonCache(map, config, allowNullValues);
		if (transactionAware) {
			cache = new TransactionAwareCacheDecorator(cache);
		}
		Cache oldCache = instanceMap.putIfAbsent(name, cache);
		if (oldCache != null) {
			cache = oldCache;
		}
		else {
			map.setMaxSize(config.getMaxSize());
		}
		return cache;
	}

	@Override
	public Collection<String> getCacheNames() {
		return Collections.unmodifiableSet(configMap.keySet());
	}

}
